﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

namespace Cobalt.Content.Pipeline.Graphics.Illumination
{
	public abstract class LightContent : NodeContent
	{
		public Vector3 LightValue
		{
			get { return OpaqueData.GetValue( "LightValue", Vector3.One ); }
			set
			{
				if( value.X < 0 || value.Y < 0 || value.Z < 0 )
					throw new ArgumentOutOfRangeException();

				OpaqueData["LightValue"] = value;
			}
		}

		public float ConstantAttenuation
		{
			get { return OpaqueData.GetValue( "ConstantAttenuation", 1.0F ); }
			set
			{
				if( value < 0 )
					throw new ArgumentOutOfRangeException();

				OpaqueData["ConstantAttenuation"] = value;
			}
		}

		public float LinearAttenuation
		{
			get { return OpaqueData.GetValue( "LinearAttenuation", 1.0F ); }
			set
			{
				if( value < 0 )
					throw new ArgumentOutOfRangeException();

				OpaqueData["LinearAttenuation"] = value;
			}
		}

		public float QuadraticAttenuation
		{
			get { return OpaqueData.GetValue( "QuadraticAttenuation", 0.0F ); }
			set
			{
				if( value < 0 )
					throw new ArgumentOutOfRangeException();

				OpaqueData["QuadraticAttenuation"] = value;
			}
		}

		internal LightContent()
		{
		}

		public float GetRange( float lumThresh )
		{
			if( lumThresh < 1e-5F )
				lumThresh = 1e-5F;

			Vector3 lightValue = LightValue;
			float fullLum = lightValue.X * 0.3F + lightValue.Y * 0.59F + lightValue.Z * 0.11F;

			if( fullLum < lumThresh )
				return 0;

			float aC = ConstantAttenuation;
			float aL = LinearAttenuation;
			float aQ = QuadraticAttenuation;

			if( aQ > 1e-7F )
			{
				//quadratic
				return (-aL * lumThresh + (float)Math.Sqrt( aL * aL * lumThresh * lumThresh -
					4 * aQ * lumThresh * (aC * lumThresh - fullLum) )) /
					(2 * aQ * lumThresh);
			}
			else if( aL >= 1e-7F )
			{
				return (fullLum / lumThresh - aC) / aL;
			}
			else
			{
				return float.PositiveInfinity;
			}
		}
							   
		internal Vector3 Position;
		internal Vector3 LightColor;
		internal Vector3 AttenParams;

		internal const float LuminanceThreshhold = 0.01F;

		internal void Setup()
		{
			Position = AbsoluteTransform.Translation;

			AttenParams.X = ConstantAttenuation;
			AttenParams.Y = LinearAttenuation;
			AttenParams.Z = QuadraticAttenuation;

			LightColor = LightValue;

			InternalSetup();
		}

		protected internal abstract void InternalSetup();

		protected internal abstract bool LightsBox( ref BoundingBox box );
		protected internal abstract bool LightsPoint( ref Vector3 point );
	}

	public class PointLightContent : LightContent
	{
		public PointLightContent()
		{
		}

		internal BoundingSphere Bounds;

		protected internal override void InternalSetup()
		{
			Bounds = new BoundingSphere( Position, GetRange( LuminanceThreshhold ) );
		}

		protected internal override bool LightsBox( ref BoundingBox box )
		{
			bool ret;
			Bounds.Intersects( ref box, out ret );
			return ret;
		}

		protected internal override bool LightsPoint( ref Vector3 point )
		{
			ContainmentType ret;
			Bounds.Contains( ref point, out ret );
			return ret != ContainmentType.Disjoint;
		}
	}

	public class SpotLightContent : LightContent
	{
		public Vector3 Direction
		{
			get { return OpaqueData.GetValue( "Direction", Vector3.UnitZ ); }
			set
			{
				if( Helpers.SafeNormalize( ref value ) == 0 )
					throw new ArgumentException();

				OpaqueData["Direction"] = value; 
			}
		}

		private const float MinSpotAngle = 1 * Helpers.DegToRad;
		private const float MaxSpotAngle = 90 * Helpers.DegToRad;

		public float InnerAngle
		{
			get { return OpaqueData.GetValue( "InnerAngle", 25.0F * (float)(Math.PI / 90.0) ); }
			set
			{
				if( value < MinSpotAngle || value > MaxSpotAngle )
					throw new ArgumentOutOfRangeException();

				OpaqueData["InnerAngle"] = value;
			}
		}

		public float OuterAngle
		{
			get { return OpaqueData.GetValue( "OuterAngle", 30.0F * (float)(Math.PI / 90.0) ); }
			set
			{
				if( value < MinSpotAngle || value > MaxSpotAngle )
					throw new ArgumentOutOfRangeException();

				OpaqueData["OuterAngle"] = value;
			}
		}

		public SpotLightContent()
		{
		}

		internal Vector3 DirectionVector;
		internal Vector2 Angles, CosAngles;
		internal float Range;
		internal Matrix LightMatrix;
		internal BoundingFrustum Bounds;

		protected internal override void InternalSetup()
		{
			DirectionVector = Direction;
			Angles = new Vector2( InnerAngle, OuterAngle );
			CosAngles = new Vector2( (float)Math.Cos( Angles.X ), (float)Math.Cos( Angles.Y ) );

			Vector3 target = Position + DirectionVector;
			
			Vector3 up = Vector3.Cross( DirectionVector, Vector3.UnitX );
			if( up.LengthSquared() < 0.01F )
				up = Vector3.Cross( DirectionVector, Vector3.UnitY );
			if( up.LengthSquared() < 0.01F )
				up = Vector3.Cross( DirectionVector, Vector3.UnitZ );

			Range = GetRange( LightContent.LuminanceThreshhold );

			if( Range <= 0.001F )
				return;

			Matrix matView = Matrix.CreateLookAt( Position, target, up );
			Matrix matProj = Matrix.CreatePerspectiveFieldOfView( Math.Min( Angles.Y * 2 + 4 * Helpers.DegToRad, 179 * Helpers.DegToRad ), 1, 0.001F, Range );

			LightMatrix = matView * matProj;
			Bounds = new BoundingFrustum( LightMatrix );
		}

		protected internal override bool LightsBox( ref BoundingBox box )
		{
			if( Bounds == null )
				return false;

			bool ret;
			Bounds.Intersects( ref box, out ret );
			return ret;
		}

		protected internal override bool LightsPoint( ref Vector3 point )
		{
			if( Bounds == null )
				return false;

			ContainmentType ret;
			Bounds.Contains( ref point, out ret );
			return ret != ContainmentType.Disjoint;
		}
	}
}
